package zpe.genericcrud.dao;


import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTemplate;

import zpe.genericcrud.model.Entidade;

@SuppressWarnings("unchecked")

/**
 * Gerenciador gen�rico de acesso ao banco de dados. Cont�m m�todos b�sicos de opera��es com o banco de dados n�o transacionados.
 * @author Elias Sales, Samuel Soares
 *
 * @param <E> Entidade, que extende genericcrud.model.Entidade, manipulada no banco de dados.
 */

// @Repository("daoJpa")
public class GenericDaoJpa<E extends Entidade>{
	
	/** Trata da manipula��o de dados com hibernate */
	// @Resource(name = "jpaTemplate")
	private JpaTemplate jpaTemplate;
	
	/** Cont�m as propriedades de configura��o do banco de dados. */
	// @Resource(name = "dataSource")
	private DriverManagerDataSource dataSource;
	
	/**
	 * Consulta todas as tuplas da entidade no banco de dados na ordem em que eles est�o no banco. 
	 * @param clazz Classe para consulta
	 * @return Lista com todos os objetos correspondentes �s tuplas da tabela. 
	 */
	public List<E> findAll( Class<?> clazz ) {
		return getJpaTemplate().find( "from " + clazz.getName() );//e.g. from Entidade
	}

	/**
	 * Consulta todas as tuplas da entidade no banco de dados na ordem definida em campoOrdenacao.
	 * @param clazz Classe para consulta
	 * @param campoOrdenacao Atributos que far�o a ordena��o dos dados. Se houver mais de um campo de ordena��o, 
	 * 		eles ter�o que estar separados por v�rgula numa mesma string <br/>  
	 * 		<p>E.g.: Classe carro. <br/>
	 * 			Ordena��o pelo campo placa - findAll( Carro.class, "placa" ); <br/>
	 * 			Ordena��o pelos campos cor e placa - findAll( Carro.class, "cor, placa" ); <br/>
	 * 			Ordena��o pelos campos cor e placa em ordem descendente - findAll( Carro.class, "cor, placa desc" ).</p>		  
	 * @return Lista com todos os objetos correspondentes �s tuplas da tabela ordenados de acordo com a defini��o de campoOrdenacao.
	 */
	public List<E> findAll( Class<?> clazz, String campoOrdenacao ) {
		return getJpaTemplate().find( "from " + clazz.getName() + " order by " + campoOrdenacao );
	}
	
	/**
	 * Consulta todas as tuplas da entidade no banco baseado nos atributos preenchidos do objeto. 
	 * Obs.: S� � feita consulta com o operador =; like n�o � utilizado.
	 * @param obj Objeto com dados para consulta.
	 * @return Lista com todos os objetos correspondentes �s tuplas da tabela que satisfazem os dados do objeto.
	 */
	@Deprecated
	public List<E> findByExample(E obj) {
		return null; //getEntityManager().findByExample( obj ); // TODO implementar findByExample ...
	}

	/**
	 * Consulta uma entidade pela sua chave prim�ria.
	 * @param clazz Classe para consulta.
	 * @param id Id do objeto.
	 * @return Objeto que possui o id correspondente, null se n�o for encontrado o id.
	 */
	public E findByPrimaryKey(Class<?> clazz, Serializable id) {
		return (E)getJpaTemplate().find(clazz, id);
	}

	/**
	 * Salva um objeto no banco de dados.
	 * @param e Objeto a ser salvo.
	 * @return Objeto salvo.
	 */
	public E insert(E e) {
		getJpaTemplate().persist( e );
		return e;
	}

	/**
	 * Salva uma cole��o de objetos no banco de dados. Se algum objeto da cole��o existir no banco (id preenchido), ele � atualizado. 
	 * @param collection Cole��o a ser salva.
	 */
	public void insertAll(Collection<E> collection) {
		for (E e : collection) 
			getJpaTemplate().persist(e); //getEntityManager().saveOrUpdateAll( collection );		
	}
	
	/**
	 * Remove um objeto do banco de dados.
	 * @param entidade Objeto a ser removido.
	 */
	public void remove(E entidade) {
		getJpaTemplate().remove(getJpaTemplate().merge(entidade));
	}

	/**
	 * Remove uma cole��o de objetos do banco de dados.
	 * @param collection Cole��o a ser removida.
	 */
	public void removeAll(final Collection<E> collection) {
		for (E entidade : collection) {
			remove(entidade);
		}
	}

	/**
	 * Atualiza um objeto no banco de dados. Se o objeto n�o existir (id nulo) ele � salvo.
	 * @param e Objeto a ser atualizado.
	 * @return Objeto atualizado. 
	 */
	public E update(E e) {
		getJpaTemplate().merge(e); //getEntityManager().saveOrUpdate(e);
		return e;
	}

	/**
	 * Atualiza uma cole��o de objetos no banco de dados. Se algum objeto da cole��o n�o existir no banco (id nulo), ele � salvo.
	 * @param collection Cole��o a ser atualizada.
	 */
	public void updateAll(Collection<E> collection) {
	    for (E e : collection)
			getJpaTemplate().merge(e); // getEntityManager().saveOrUpdateAll(collection);		
	}
	
	/**
	 * Consulta todas as tuplas da entidade cuja descri��o come�a com strStart. Os dados s�o ordenados pela descri��o. 
	 * @param clazz Classe para consulta.
	 * @param strStart Valor da descri��o para consulta.
	 * @return Lista com todos os objetos correspondentes �s tuplas da tabela cuja descri��o come�a com strStart ordenados pela descri��o.
	 */
	public List<E> startsWith( Class<E> clazz, String strStart ){
		 return getJpaTemplate().find( "from " + clazz.getName() + 
				 " where descricao like '" + strStart.toLowerCase() + "%' or descricao like '" + strStart.toUpperCase() + "%' order by descricao" );
	}
	
	/**
	 * Consulta todas as tuplas da entidade cuja descri��o come�a com strStart e que satisfazem os dados do filtro filter.
	 * Este filtro refere-se a atributos da classe com dados preenchidos. Os dados s�o ordenados pela descri��o.
	 * @param clazz Classe para consulta.
	 * @param strStart Valor da descri��o para consulta.
	 * @param filter Filtro para consulta. A chave corresponde ao nome do atributo; para cada chave deve estar associado um valor.  
	 * @return Lista com todos os objetos correspondentes �s tuplas da tabela cuja descri��o come�a com strStart filtrados pelos atributos em filter ordenados pela descri��o.
	 */
	public List<E> startsWithAndFilter( Class<E> clazz, String strStart, Map<String, Object> filter ){
		
		StringBuffer query = new StringBuffer();
		query.append( "from " + clazz.getName() + " where ( descricao like '" + strStart.toLowerCase() + "%' or descricao like '" + strStart.toUpperCase() + "%' )" );
		Set<String> setKey = filter.keySet();
		for( String key : setKey ){
			Object valor = filter.get( key );
			/*Se o atributo for do tipo String ou Character, � usada a cl�usula like*/
			if( valor instanceof String 
					|| valor instanceof Character ){
				query.append( " and ( " + key + " like '%" + valor.toString().toLowerCase() + "%' or " + key + " like '%" + valor.toString().toUpperCase() + "%') ");
			}/*Se o atributo for do tipo java.util.Date, a data � formatada no formato yyyy-MM-dd*/
			else if( valor instanceof Date ){
				SimpleDateFormat df = new SimpleDateFormat( "yyyy-MM-dd" );
				query.append( " and " + key + " = '" + df.format(valor) + "' ");
			}else{
				query.append( " and " + key + " = " + valor );
			}
		}		
		return getJpaTemplate().find( query.toString() );
	} 
	
	/**
	 * Consulta todas as tuplas da entidade que satisfazem os dados do filtro filter.
	 * Este filtro refere-se a atributos da classe com dados preenchidos.
	 * @param clazz Classe para consulta.
	 * @param filter Filtro para consulta. A chave corresponde ao nome do atributo; para cada chave deve estar associado um valor.
	 * @return Lista com todos os objetos correspondentes �s tuplas da tabela filtrados pelos atributos em filter.
	 */
	public List<E> findByFilter( Class<E> clazz, Map<String, Object> filter ){
		String query = montaQuery(clazz, filter, null);		
		return getJpaTemplate().find( query );
	}	
	
	/**
	 * Consulta todas as tuplas da entidade que satisfazem os dados do filtro filter e ordena o resultado pelos atributos de campoOrdenacao.
	 * O filtro refere-se a atributos da classe com dados preenchidos.
	 * @param clazz Classe para consulta.
	 * @param filter Filtro para consulta. A chave corresponde ao nome do atributo; para cada chave deve estar associado um valor.
	 * @param campoOrdenacao Atributos que far�o a ordena��o dos dados. Se houver mais de um campo de ordena��o, 
	 * 		eles ter�o que estar separados por v�rgula numa mesma string <br/>  
	 * 		<p>E.g.: Classe Carro e fitro filter. <br/>
	 * 			Ordena��o pelo campo placa - findByFilter( Carro.class, filter, "placa" ); <br/>
	 * 			Ordena��o pelos campos cor e placa - findByFilter( Carro.class, filter, "cor, placa" ); <br/>
	 * 			Ordena��o pelos campos cor e placa em ordem descendente - findByFilter( Carro.class, filter, "cor, placa desc" ).</p>
	 * @return Lista com todos os objetos correspondentes �s tuplas da tabela filtrados pelos atributos em filter.
	 */
	public List<E> findByFilter( Class<E> clazz, Map<String, Object> filter, String campoOrdenacao ){
		String query = montaQuery(clazz, filter, campoOrdenacao);
		return getJpaTemplate().find( query.toString() );
	}	
	
	/**
	 * Monta a query para a entidade baseada no filtro filter e, se houver, no campo de ordena��o.
	 * @param clazz Classe para montagem da query.
	 * @param filter Filtro usando na cl�usula where.
	 * @param campoOrdenacao Campos de ordena��o.
	 * @return Query montada.
	 */
	private String montaQuery( Class<E> clazz, Map<String, Object> filter, String campoOrdenacao ){
		StringBuffer query = new StringBuffer();
		/*Necess�rio para fazer o and em todos os atributos a partir do primeiro*/
		query.append( "from " + clazz.getName() + " where 1=1 " );
		Set<String> setKey = filter.keySet();
		for( String key : setKey ){
			Object valor = filter.get( key );
			/*Se o atributo for do tipo String ou Character, � usada a cl�usula like*/
			if( valor instanceof String 
					|| valor instanceof Character ){
				query.append( " and ( " + key + " like '%" + valor.toString().toLowerCase() + "%' or " + key + " like '%" + valor.toString().toUpperCase() + "%') ");
			}/*Se o atributo for do tipo java.util.Date, a data � formatada no formato yyyy-MM-dd*/
			else if( valor instanceof Date ){
				SimpleDateFormat df = new SimpleDateFormat( "yyyy-MM-dd" );
				query.append( " and " + key + " = '" + df.format(valor) + "' ");
			}else{
				query.append( " and " + key + " = " + valor );
			}
		}
		if( campoOrdenacao != null )
			query.append( " order by " + campoOrdenacao );
		return query.toString();
	}
	public JpaTemplate getJpaTemplate() {
		return jpaTemplate;
	}

	protected void setJpaTemplate(JpaTemplate jpaTemplate) {
		this.jpaTemplate = jpaTemplate;
	}

	protected DriverManagerDataSource getDataSource() {
		return dataSource;
	}

	protected void setDataSource(DriverManagerDataSource dataSource) {
		this.dataSource = dataSource;
	}
}